/********************************************************************
 	created:	14/10/2005   23:07
 	filename: 	glmath.h
 	author:		GMKal (exile79@gmail.com)
 	
 	purpose:	math stuff for OpenGL programs
 
    last modified:	 1/11/2005   23:07 
 *********************************************************************/
 


#ifndef GLMATH_H
#define GLMATH_H

#include <windows.h>
#include <cmath>
#include <float.h>
#include <gl/GL.h>

#include <utility>
#include <limits>

//#include <pair>

//////////////////////////////////////////////////////////////////////////
// use float numbers by defining USEFLOAT. 
// normally floats are faster but less accurate
//#define USEFLOAT
//#ifdef USEFLOAT
//typedef float Real;
//#else	//use doubles instead of floats
//typedef double Real;
//#endif

#define PI				3.1415926535897932384626433832795
#define	PIdiv2			1.5707963267948966192313216916398
#define PIdiv3			1.0471975511965977461542144610932
#define PIdiv4			0.78539816339744830961566084581988


#define TWO_PI			6.283185307179586476925286766559
#define PIdiv180		0.017453292519943295769236907684886
#define _180divPI	57.295779513082320876798154814105
#define SMALL_REAL		0.00001
#define BIG_REAL		1000000000.0


namespace glmath
{


//////////////////////////////////////////////////////////////////////////
//small inline useful functions

//min - max functions
template<class t> inline t Max(t a,t b) { return (a<b)?b:a; }
template<class t> inline t Min(t a,t b) { return (a<b)?a:b; }


//clamp x between lo and hi
template<class t> inline t Clamp(t x,t lo,t hi) { return (x<lo)?lo:((x>hi)?hi:x); }


//linear interpolation
template<class t> inline t Lerp(t lo,t hi,t percent) { return (lo + percent*(hi-lo)); }


//trigonometric conversions
inline float Rad2Deg(double radians) { return float(radians*_180divPI); }
inline double Deg2Rad(float degrees) { return degrees*PIdiv180; }

// check if to real numbers are close enough...
inline bool CompareTolerant(float a,float b) { return (abs(a-b)<SMALL_REAL)?true:false;	}

/** random functions*/
template<class t> inline t Rand( void ) { return (t)rand(); }
#define Randf Rand<float>
#define Randd Rand<double>
#define Randi Rand<int>
template<class t> inline t UnitRand( void ) { return ((t)rand() / (t)RAND_MAX); }
#define UnitRandf UnitRand<float>
#define UnitRandd UnitRand<double>
#define UnitRandi UnitRand<int>
template<class t> inline t RangeRand( t low, t high) { return (high-low)*UnitRand<t>() + low; }
#define RangeRandf RangeRand<float>
#define RangeRandd RangeRand<double>
#define RangeRandi RangeRand<int>


	
class Vector3
{
public:
	GLfloat x,y,z;

public:
	//from 3 floats
	Vector3(float x=0,float y=0,float z=0):x(x),y(y),z(z) {}

	/*
	 *	from a 3-member array
	 *	for performance reasons, I don't check if the array has 3 members.....
	 *	so just be careful what you input...... 
	 */
	Vector3(const float* rarray):x(rarray[0]),y(rarray[1]),z(rarray[2]) {}


	void Set(float x,float y,float z);
	void Set(float* array);	
	Vector3 CrossProduct(const Vector3& v) const;
	float DotProduct(const Vector3& v) const;
	float LengthSquared() const;
	float Length() const;
	Vector3 Normalized() const;
	void Normalize();
	void Lerp(const Vector3& v1,const Vector3& v2,const float r);


	/*
	 *	operator overloads
	 */
	//indexing (write) -- avoid using it very slow......
	GLfloat& operator[](const int i) { return *((&x)+i); }
	//indexing (read-only)
	const GLfloat& operator[](const int i)const { return *((&x)+i); }

	
	bool operator== (const Vector3& v) const;
	bool operator!= (const Vector3& v) const;
	
	Vector3 operator- () const; 
	
	void operator+= (const Vector3& v);
	void operator-= (const Vector3& v);
	void operator*= (const float f);
	void operator/= (const float f);
			
	Vector3 operator+(const Vector3& v) const;
	Vector3 operator-(const Vector3& v) const;
	Vector3 operator*(const float r) const;
	Vector3 operator/(const float r) const;

	bool operator < (const Vector3& v) const;
	bool operator > (const Vector3& v) const;
	
	friend Vector3 operator*(const float r,const Vector3& v);
	
		
	
		
	friend Vector3 VectorAdd(const Vector3& v1,const Vector3& v2);
	friend void VectorAdd(const Vector3& v1,const Vector3& v2, Vector3& vsum);
	friend Vector3 VectorSubtract(const Vector3& v1,const Vector3& v2);
	friend void VectorSubtract(const Vector3& v1,const Vector3& v2,Vector3& vdiff);
	friend float Dot3(const Vector3& v1,const Vector3& v2);
	friend Vector3 Cross3(const Vector3& v1,const Vector3& v2);
	friend void Cross3(const Vector3& v1,const Vector3& v2,Vector3& vcross);
	friend float RadiansBetweenVectors(const Vector3& v1,const Vector3& v2);
	friend Vector3 ClosestPointOnLine(const Vector3& vstart,const Vector3& vend,
		const Vector3& p);
	friend bool IsPointInsidePolygon(const Vector3& vpoint, const Vector3 vpoly[],
		int verticecount);
	friend float Distance(const Vector3& v1,const Vector3& v2);

	friend void glVertex3(const Vector3& v);
	friend void glNormal3(const Vector3& v);
	friend void glTranslate(const Vector3& v);	

	// special points
	static const Vector3 ZERO;
	static const Vector3 UNIT_X;
	static const Vector3 UNIT_Y;
	static const Vector3 UNIT_Z;
	static const Vector3 NEGATIVE_UNIT_X;
	static const Vector3 NEGATIVE_UNIT_Y;
	static const Vector3 NEGATIVE_UNIT_Z;
	static const Vector3 UNIT_SCALE;
};



	


//////////////////////////////////////////////////////////////////////////
// the Vector2 class

class Vector2
{
public:
	GLfloat x,y;

public:
	/*
	 *	from 3 floats
	 *	for uniformity of interface with the Vector3, we allow a 
	 *	three-arg constructor. The 3rd argument is not used though
	 */
	Vector2(float x=0,float y=0,float z=0) :x(x),y(y) {};

	/*
	 *	from a 2-member array
	 *	for performance reasons, I don't check if the array has at 
	 *	least 2 members..... so just be careful what you input...... 
	 */
	Vector2(const float* rarray) :x(rarray[0]),y(rarray[1]) {}


	void Set(float x,float y,float z=0); 	
	void Set(const float* array);

	Vector3 CrossProduct(const Vector2& v) const;
	float DotProduct(const Vector2& v) const;
	float Length() const;
	float LengthSquared() const;
	Vector2 Normalized() const;
	void Normalize();
	const Vector2& Lerp(const Vector2& v1,const Vector2& v2,const float r);
	

	
	/*
	 *	operator overloads
	 */

	//indexing (write)
	float& operator[](const int i) { return *((&x)+i); }
	//indexing (read-only)
	const float& operator[] (const int i) const { return *((&x)+i); }
	
	bool operator== (const Vector2& v) const;
	bool operator!= (const Vector2& v) const;
	Vector2 operator- () const;
	void operator+= (const Vector2& v);
	void operator-= (const Vector2& v);
	void operator*= (const float r);
	void operator/= (const float r);
	
	Vector2 operator+(const Vector2& v) const;
	Vector2 operator-(const Vector2& v) const;
	Vector2 operator*(const float r) const;
	Vector2 operator/(const float r) const;

	friend Vector2 operator*(const float r,const Vector2& v);
	
	friend Vector2 VectorAdd(const Vector2& v1,const Vector2& v2);
	friend void VectorAdd(const Vector2& v1,const Vector2& v2,Vector2& vsum);
	friend Vector2 VectorSubtract(const Vector2& v1,const Vector2& v2);
	friend void VectorSubtract(const Vector2& v1,const Vector2& v2,Vector2& vdiff);
	
	
	/*
	 *	opengl helpers
	 */
	friend void glVertex2(const Vector2& v);
	friend void glTexCoord2(const Vector2& v);	
};





/************************************************************************/
/* a quaternion class													*/
/*																		*/
/*																		*/
/*																		*/
/*																		*/
/*																		*/
/************************************************************************/
class Matrix;	

class Quaternion
{
public:
		float x,y,z,w;

public:

	//////////////////////////////////////////////////////////////////////////
	// constructors
	Quaternion(float x=0.0f,float y=0.0f, float z=0.0f,float w=1.0f);
	Quaternion(const Quaternion& q1,const Quaternion& q2,float& interp);
	
	
	/*
	 *	operator overloads
	 */
	Quaternion operator*(const Quaternion& q) const;
	Quaternion operator+(const Quaternion& q) const;
	void operator*= (const Quaternion& q);
	void operator*= (const float f);
	Quaternion operator* (const float r) const;
	

	float& operator[](int i){return *(&x+i);}				//indexing (write)
	const float& operator[](int i) const {return *(&x+i);}	//indexing (read-only)


	//////////////////////////////////////////////////////////////////////////
	void Set(float x1,float y1, float z1,float w1);
	void Identity();
	float Length() const;
	float SqrLength() const;
	void Normalize();
	Quaternion Conjugate();
	void Inverse();
	Quaternion CrossProduct(const Quaternion& q) const;
	void CreateMatrix(float mat[16],bool bNormalize=true);
	Matrix CreateMatrix(bool bNormalize=true);
	void Slerp(const Quaternion& q1,const Quaternion& q2,float interp);

	const Quaternion& SetFromAxis(double radians,const Vector3& axis);
	const Quaternion& SetFromAxis(double radians,float xaxis,float yaxis,float zaxis);
	const Quaternion& SetFromRadians(double xangle,double yangle,double zangle);

	void RotateVector(Vector3& v, bool bNormalize=true);
	Vector3 GetDirectionVector() const;	
};




/************************************************************************/
/*	4x4 matrix class													*/
/*																		*/
/*																		*/
/*																		*/
/*																		*/
/************************************************************************/
class Matrix
{
public:
	GLfloat data[16];

public:

	//////////////////////////////////////////////////////////////////////////
	//constructors

	//default values define an identity matrix
	Matrix();	
	Matrix(GLfloat _00, GLfloat _01, GLfloat _02, GLfloat _03,
		GLfloat _10, GLfloat _11, GLfloat _12, GLfloat _13,
		GLfloat _20, GLfloat _21, GLfloat _22, GLfloat _23,
		GLfloat _30, GLfloat _31, GLfloat _32, GLfloat _33);

	
	
	void Identity();	// "reset" matrix
	void Set(GLfloat _00, GLfloat _01, GLfloat _02, GLfloat _03,
		GLfloat _10, GLfloat _11, GLfloat _12, GLfloat _13,
		GLfloat _20, GLfloat _21, GLfloat _22, GLfloat _23,
		GLfloat _30, GLfloat _31, GLfloat _32, GLfloat _33);
	void Set(GLfloat mat[16]);
    
	void SetScale(float x,float y,float z);
	void SetTranslation(float x,float y,float z);
	void SetTranslation(const Vector3& v);	
	void SetRotation(double angle,float x,float y,float z);
	void SetRotation(double angle,const Vector3& axis);
	void SetRotation(double xangle,double yangle, double zangle);	
	void SetInvRotation(double xangle,double yangle, double zangle);
    void SetFromQuaternion(Quaternion& quat);    	
	
	void Transpose();
	void Inverse();
	Matrix Inversed() const;

	void CreateShadowMatrix(const Vector3& planeNormal,const Vector3& lightPos);
	void CreateShadowMatrix(const Vector3 points[3],const Vector3& lightPos);

	/*
     *	matrix operator overloads
	 */
	Matrix operator-(const Matrix& m) const;
	Matrix operator+(const Matrix& m) const;
	Matrix operator*(const Matrix& m) const;
	Matrix operator/(const Matrix& m) const;

	void operator-=(const Matrix& m);
	void operator+=(const Matrix& m);
	void operator*=(const Matrix& m);
	void operator/=(const Matrix& m);

	Matrix operator-(const float& m) const;
	Matrix operator+(const float& m) const;
	Matrix operator*(const float& m) const;
	Matrix operator/(const float& m) const;

	void operator-=(const float& m);
	void operator+=(const float& m);
	void operator*=(const float& m);
	void operator/=(const float& m);

	bool operator==(const Matrix& m) const;
	bool operator!=(const Matrix& m) const;

	GLfloat& operator[](int i) {return data[i];}				//indexing (write)
	const GLfloat& operator[] (int i) const {return data[i];}	//indexing (read-only)

	friend Vector3 operator* (const Vector3& v,const Matrix& m);
	
	/*
	 * vector operations
	 */
	Vector3 VectorMultiply(const Vector3& v) const;
	void VectorTransform(Vector3& v) const;
	void VectorRotate(Vector3& v) const;
	void VectorInvRotate(Vector3& v) const;
	void VectorTranslate(Vector3& v) const;
	void VectorInvTranslate(Vector3& v) const;


	/*
	 *	OpenGL useful operations
	 */
	friend void glLoadMatrix(const Matrix& m);
	friend void glMultMatrix(const Matrix& m);
	friend void glGetProjection(Matrix& m);
	friend void glGetModelview(Matrix& m);

};





/************************************************************************/
/* Plane class															*/
/*																		*/
/*																		*/
/*																		*/
/************************************************************************/
class Plane
{
public:

	//
	// the plane equation is Ax+By+Cz+D=0
	// or X.N+D=0
	// where X=(x,y,z)
	// N is the normal of the plane
	// and D is the intercept of the plane
	Vector3 m_normal;		//X.N+intercept=0
	float	m_intercept;	//distance from the origin


	enum enumPointClassification
	{	POINT_ON_PLANE,	
		POINT_IN_FRONT_OF_PLANE, 
		POINT_BEHIND_PLANE };


	//
	// operators	
	bool operator==(const Plane & rhs) const;
	bool operator!=(const Plane & rhs) const
	{		return!((*this)==rhs);	}

	Plane operator-(void) const {return Plane(-m_normal, -m_intercept);}
	Plane operator+(void) const {return (*this);}

	Plane() : m_intercept(0.0), m_normal(0,1,0)  {}
	Plane(const Vector3 &n,float intercept) : m_normal(n),m_intercept(intercept) {}
	Plane(const Vector3& v1,const Vector3& v2,const Vector3& v3)
	{	SetFromPoints(v1,v2,v3);	}


	void SetNormal(const Vector3 &v) { m_normal=v;	} 
	void SetIntercept(float inter) { m_intercept=inter; }
	void SetFromPoints(const Vector3& v1,const Vector3& v2,const Vector3& v3);

	void CalcIntercept(const Vector3& pointOnPlane)
	{	m_intercept = -Dot3(m_normal,pointOnPlane);	}

	void Normalize();

	Vector3 GetNormal() const {	return m_normal;	}
	float GetIntercept() const { return m_intercept;	}

	bool Intersect3(const Plane& p2,const Plane& p3,Vector3& vresult);
	float Distance(const Vector3& v) const;
	int ClassifyPoint(const Vector3& v) const;
	Plane Lerp(const Plane& p2,float factor);

};







/************************************************************************/
/*	set of bits to represent many true/falses  

*/
/************************************************************************/
class Bitset
{
protected:
	int m_numbytes;
	unsigned char* m_pbits;

public:
	Bitset():m_numbytes(0),m_pbits(NULL) {};
	~Bitset()
	{
		delete[] m_pbits;
		m_pbits=NULL;
	}

	bool Init(int numberOfBits);
	void ClearAll();
	void SetAll();

	void Clear(int bitNumber);
	void Set(int bitNumber);

	unsigned char IsSet(int bitNumber);
};




/************************************************************************/
/*	BezierCurve															*/
/*																		*/
/*																		*/
/************************************************************************/

class BezierCurve
{
public:
	BezierCurve() {};
	BezierCurve(Vector3& p1,Vector3& p2,Vector3& p3,Vector3& p4);

	Vector3 pstart;
	Vector3 pend;
	Vector3 pcontrol1;
	Vector3 pcontrol2;

	void Set(const Vector3& p1,const Vector3& p2,const Vector3& p3,const Vector3& p4);
	Vector3 PointOnCurve(const float time) const;
};



/************************************************************************/
/* bounding sphere class                                                */
/*																		*/
/*																		*/
/*																		*/
/************************************************************************/
class BoundingSphere
{
public:
	Vector3 m_center;
	float	m_radius;

	enum enumPointClassification 
	{	
		POINT_INSIDE_SPHERE,
		POINT_ON_SPHERE,
		POINT_OUTSIDE_SPHERE
	};

	BoundingSphere(const Vector3& v=Vector3(),float r=1.0f) : m_center(v),m_radius(r) {}

	//TODO make m_center and m_radius private and replace them with the following accessors
	Vector3 GetCenter(void) const { return m_center; }
	void SetCenter(const Vector3& center) { m_center=center; }
	void SetCenter(float x, float y,float z) { m_center.x=x; m_center.y=y; m_center.z=z; }
	float GetRadius(void) const { return m_radius; }
	void SetRadius(float radius) {m_radius=radius;}

	bool Collides(const BoundingSphere& bs) const;
	bool IsPointInside(const Vector3& v) const;	
	enumPointClassification ClassifyPoint(const Vector3& v) const;
};


/************************************************************************/
/* bounding box class	                                                */
/*																		*/
/*																		*/
/*																		*/
/************************************************************************/
class BoundingBox
{
public:


	enum enumPointClassification 
	{	
		POINT_INSIDE_BOX,
		POINT_ON_BOX,
		POINT_OUTSIDE_BOX,
		POINT_ABOVE_BOX,
		POINT_BELLOW_BOX,
		POINT_LEFT_OF_BOX,
		POINT_RIGHT_OF_BOX,
		POINT_IN_FRONT_OF_BOX,
		POINT_BACK_OF_BOX
	};

	BoundingBox(float xdim=1.f,float ydim=1.f,float zdim=1.f,Vector3 center=Vector3(0,0,0))
		: m_xdim(xdim),m_ydim(ydim),m_zdim(zdim),m_center(center) {};

	bool Collides(const BoundingBox& bb) const;
	bool IsPointinside(const Vector3& v) const;
	bool LocatePoint(const Vector3& v, enumPointClassification where) const;

	enumPointClassification ClassifyPoint(const Vector3& v) const;

	Vector3 GetCenter(void) const { return m_center; }
	void SetCenter(const Vector3& center) { m_center=center; }
	void SetCenter(float x,float y,float z){ m_center.x=x; m_center.y=y; m_center.z=z; }
	float GetXDim(void) const { return m_xdim; }
	void SetXDim(float x) { m_xdim=x;}
	float GetYDim(void) const { return m_ydim; }
	void SetYDim(float y) { m_ydim=y;}
	float GetZDim(void) const { return m_zdim; }
	void SetZDim(float z) { m_zdim=z;}

	inline Vector3 GetMin(void) const { return Vector3(m_center.x-m_xdim,m_center.y-m_ydim,m_center.z-m_zdim); }
	inline Vector3 GetMax(void) const { return Vector3(m_center.x+m_xdim,m_center.y+m_ydim,m_center.z+m_zdim); }

	Vector3 m_center;
	
	float m_xdim;	// x distance from the center to the edge of the box
	float m_ydim;	// y distance from the center to the edge of the box
	float m_zdim;	// z distance from the center to the edge of the box

};




/************************************************************************/
/*	Frustum class for culling											*/
/*																		*/
/*																		*/
/*																		*/
/*																		*/
/************************************************************************/
class Frustum
{
protected:
	Plane m_planes[6];	

public:
	enum enumSIDEPLANES
	{
		LEFT_PLANE=0,
		RIGHT_PLANE,
		TOP_PLANE,
		BOTTOM_PLANE,
		NEAR_PLANE,
		FAR_PLANE
	};

	enum enumFRUSTUM_CLASSIFICATION
	{
		OUTSIDE_FRUSTUM=0,
		IN_FRUSTUM
	};


	void Update();
	bool IsPointInside(const Vector3& point) const;
	bool IsBoundingBoxInside(const Vector3 vertices[8]) const;
	bool IsBoundingSphereInside(const Vector3& center,float radius) const;
	bool IsBoundingSphereInside(const BoundingSphere& bs) const;
};


class Ray
{
protected:
	Vector3 m_origin;
	Vector3 m_direction;

public:
	Ray() : m_origin(Vector3::ZERO),m_direction(Vector3::UNIT_Z) {}
	Ray(const Vector3& origin, const Vector3& direction) : m_origin(origin),m_direction(direction) {}

	/** Sets the origin of the ray. */
	void SetOrigin(const Vector3& origin) {m_origin = origin;} 
	void SetOrigin(float x,float y,float z) { m_origin.x=x; m_origin.y=y; m_origin.z=z; }
	/** Gets the origin of the ray. */
	const Vector3& GetOrigin(void) const {return m_origin;} 

	/** Sets the direction of the ray. */
	void SetDirection(const Vector3& dir) {m_direction = dir;} 
	void SetDirection(float x, float y, float z) { m_direction.x=x; m_direction.y=y; m_direction.z=z; }
	/** Gets the direction of the ray. */
	const Vector3& GetDirection(void) const {return m_direction;} 

	/** Gets the position of a point t units along the ray. */
	Vector3 GetPoint(float t) const { 
		return Vector3(m_origin + (m_direction * t));
	}

	/** Gets the position of a point t units along the ray. */
	Vector3 operator*(float t) const { 
		return GetPoint(t);
	};

	/** Tests whether this ray intersects the given plane. 
	@returns A pair structure where the first element indicates whether
	an intersection occurs, and if true, the second element will
	indicate the distance along the ray at which it intersects. 
	This can be converted to a point in space by calling getPoint().
	*/
	std::pair<bool, float> Intersects(const Plane& p) const;
	
	/** Tests whether this ray intersects the given sphere. 
	@returns A pair structure where the first element indicates whether
	an intersection occurs, and if true, the second element will
	indicate the distance along the ray at which it intersects. 
	This can be converted to a point in space by calling getPoint().
	*/
	std::pair<bool, float> Intersects(const BoundingSphere& s) const;
	
	/** Tests whether this ray intersects the given box. 
	@returns A pair structure where the first element indicates whether
	an intersection occurs, and if true, the second element will
	indicate the distance along the ray at which it intersects. 
	This can be converted to a point in space by calling getPoint().
	*/
	std::pair<bool, float> Intersects(const BoundingBox& box) const;
};


}//end namespace glmath





#endif

